Skip to content

Add Windows thread affinity support - #338

Open
GagaLP wants to merge 1 commit into
masterfrom
thread-affinity-windows
Open

Add Windows thread affinity support#338
GagaLP wants to merge 1 commit into
masterfrom
thread-affinity-windows

Conversation

@GagaLP

@GagaLP GagaLP commented Jul 14, 2026

Copy link
Copy Markdown
Contributor

Thread pinning was previously a no-op on Windows: thread_pinner only emitted a warning ("Thread pinning is currently not supported on Windows.") and did not apply any affinity settings, regardless of configuration.

This PR adds a real implementation by introducing a small pthread/cpu_set_t compatibility layer (platform_specific/affinity_win32.h/.cc). The layer maps the POSIX affinity APIs already used by Celerity (sched_getaffinity/sched_setaffinity, pthread_self, pthread_get/setaffinity_np, CPU_SET/CPU_ISSET/CPU_COUNT, etc.) to their Windows equivalents (GetActiveProcessorGroupCount, GROUP_AFFINITY, SetThreadGroupAffinity).

With this compatibility layer in place, affinity_win.cc no longer requires separate pinning logic and now closely mirrors the Linux implementation. It initializes and tears down the pinning plan in the same way and pins threads to sequential cores using the same approach.

One limitation to note: Windows organizes logical processors into groups of up to 64 processors, and a single GROUP_AFFINITY mask cannot span multiple groups. If a requested core set crosses a group boundary, the layer currently emits a warning and skips pinning instead of attempting cross-group affinity. This should not affect the common case of pinning a small number of sequential cores, but remains a limitation on systems with more than 64 logical processors.

Added corresponding tests in affinity_tests.cc.

@GagaLP
GagaLP requested a review from PeterTh July 14, 2026 16:17
@github-actions

Copy link
Copy Markdown

Check-perf-impact results: (ae6918621b46271c2f10d6eb978fe95d)

❓ No new benchmark data submitted. ❓
Please re-run the microbenchmarks and include the results if your commit could potentially affect performance.

@PeterTh PeterTh left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Instead of duplicating the win.cc / unix.cc, I think the win version could just be reduced to a include of the helper header plus a include of the unix.cc, with an explanation (of the windows helper introducing compat with the unix impl).

Comment thread test/affinity_tests.cc Outdated
Comment on lines 65 to 69
#ifdef _WIN32
#define SKIP_UNSUPPORTED() SKIP("Affinity is not supported on Windows");
#define SKIP_UNSUPPORTED() // SKIP("Affinity is not supported on Windows");
#else
#define SKIP_UNSUPPORTED()
#endif

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This whole code block (and the invocation sites) can just be removed.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done!

@@ -0,0 +1,105 @@
#pragma once

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This file should have a comment at the start explaining its purpose (i.e. implementing unix-like affinity using windows APIs).

maybe the name should also be something like affinity_win32_adapter?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done!

@GagaLP
GagaLP force-pushed the thread-affinity-windows branch from 1aacd2d to 90901f4 Compare August 5, 2026 13:55

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Clang-Tidy found issue(s) with the introduced code (1/5)


#include "log.h"

using pthread_t = DWORD;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

clang-diagnostic-error
unknown type name DWORD


using pthread_t = DWORD;

struct cpu_set_t {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

clang-diagnostic-error
definition of type cpu_set_t conflicts with typedef of the same name

uint64_t bits[WORDS] = {};
};

inline constexpr unsigned CPU_SETSIZE = cpu_set_t::CPU_SETSIZE;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

clang-diagnostic-error
expected unqualified-id


inline constexpr unsigned CPU_SETSIZE = cpu_set_t::CPU_SETSIZE;

void CPU_ZERO(cpu_set_t* set);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

clang-diagnostic-error
expected unqualified-id


inline constexpr unsigned CPU_SETSIZE = cpu_set_t::CPU_SETSIZE;

void CPU_ZERO(cpu_set_t* set);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

clang-diagnostic-error
while loop outside of a function

inline constexpr unsigned CPU_SETSIZE = cpu_set_t::CPU_SETSIZE;

void CPU_ZERO(cpu_set_t* set);
void CPU_SET(unsigned cpu, cpu_set_t* set);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

clang-diagnostic-error
expected )

inline constexpr unsigned CPU_SETSIZE = cpu_set_t::CPU_SETSIZE;

void CPU_ZERO(cpu_set_t* set);
void CPU_SET(unsigned cpu, cpu_set_t* set);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

clang-diagnostic-error
expected unqualified-id


void CPU_ZERO(cpu_set_t* set);
void CPU_SET(unsigned cpu, cpu_set_t* set);
void CPU_CLR(unsigned cpu, cpu_set_t* set);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

clang-diagnostic-error
expected )


void CPU_ZERO(cpu_set_t* set);
void CPU_SET(unsigned cpu, cpu_set_t* set);
void CPU_CLR(unsigned cpu, cpu_set_t* set);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

clang-diagnostic-error
expected unqualified-id

void CPU_ZERO(cpu_set_t* set);
void CPU_SET(unsigned cpu, cpu_set_t* set);
void CPU_CLR(unsigned cpu, cpu_set_t* set);
int CPU_ISSET(unsigned cpu, const cpu_set_t* set);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

clang-diagnostic-error
expected )

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Clang-Tidy found issue(s) with the introduced code (2/5)

void CPU_ZERO(cpu_set_t* set);
void CPU_SET(unsigned cpu, cpu_set_t* set);
void CPU_CLR(unsigned cpu, cpu_set_t* set);
int CPU_ISSET(unsigned cpu, const cpu_set_t* set);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

clang-diagnostic-error
expected unqualified-id

void CPU_CLR(unsigned cpu, cpu_set_t* set);
int CPU_ISSET(unsigned cpu, const cpu_set_t* set);

int CPU_COUNT(const cpu_set_t* set);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

clang-diagnostic-error
redefinition of __sched_cpucount as different kind of symbol

void CPU_CLR(unsigned cpu, cpu_set_t* set);
int CPU_ISSET(unsigned cpu, const cpu_set_t* set);

int CPU_COUNT(const cpu_set_t* set);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

clang-diagnostic-error
expected expression

int CPU_ISSET(unsigned cpu, const cpu_set_t* set);

int CPU_COUNT(const cpu_set_t* set);
int CPU_EQUAL(const cpu_set_t* a, const cpu_set_t* b);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

clang-diagnostic-error
expected )

int CPU_ISSET(unsigned cpu, const cpu_set_t* set);

int CPU_COUNT(const cpu_set_t* set);
int CPU_EQUAL(const cpu_set_t* a, const cpu_set_t* b);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

clang-diagnostic-error
expected parameter declarator

static constexpr unsigned PROCS_PER_GROUP = 64;

struct cpu_topology_entry {
WORD group;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

clang-diagnostic-error
unknown type name WORD


struct cpu_topology_entry {
WORD group;
WORD count;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

clang-diagnostic-error
unknown type name WORD

using cpu_topology = std::vector<cpu_topology_entry>;

struct windows_topology_policy {
static WORD get_group_count() { return GetActiveProcessorGroupCount(); }

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

clang-diagnostic-error
unknown type name WORD


#include "log.h"

using pthread_t = DWORD;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

clang-diagnostic-error
unknown type name DWORD


using pthread_t = DWORD;

struct cpu_set_t {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

clang-diagnostic-error
definition of type cpu_set_t conflicts with typedef of the same name

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Clang-Tidy found issue(s) with the introduced code (3/5)

uint64_t bits[WORDS] = {};
};

inline constexpr unsigned CPU_SETSIZE = cpu_set_t::CPU_SETSIZE;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

clang-diagnostic-error
expected unqualified-id


inline constexpr unsigned CPU_SETSIZE = cpu_set_t::CPU_SETSIZE;

void CPU_ZERO(cpu_set_t* set);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

clang-diagnostic-error
expected unqualified-id


inline constexpr unsigned CPU_SETSIZE = cpu_set_t::CPU_SETSIZE;

void CPU_ZERO(cpu_set_t* set);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

clang-diagnostic-error
while loop outside of a function

inline constexpr unsigned CPU_SETSIZE = cpu_set_t::CPU_SETSIZE;

void CPU_ZERO(cpu_set_t* set);
void CPU_SET(unsigned cpu, cpu_set_t* set);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

clang-diagnostic-error
expected )

inline constexpr unsigned CPU_SETSIZE = cpu_set_t::CPU_SETSIZE;

void CPU_ZERO(cpu_set_t* set);
void CPU_SET(unsigned cpu, cpu_set_t* set);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

clang-diagnostic-error
expected unqualified-id


void CPU_ZERO(cpu_set_t* set);
void CPU_SET(unsigned cpu, cpu_set_t* set);
void CPU_CLR(unsigned cpu, cpu_set_t* set);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

clang-diagnostic-error
expected )


void CPU_ZERO(cpu_set_t* set);
void CPU_SET(unsigned cpu, cpu_set_t* set);
void CPU_CLR(unsigned cpu, cpu_set_t* set);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

clang-diagnostic-error
expected unqualified-id

void CPU_ZERO(cpu_set_t* set);
void CPU_SET(unsigned cpu, cpu_set_t* set);
void CPU_CLR(unsigned cpu, cpu_set_t* set);
int CPU_ISSET(unsigned cpu, const cpu_set_t* set);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

clang-diagnostic-error
expected )

void CPU_ZERO(cpu_set_t* set);
void CPU_SET(unsigned cpu, cpu_set_t* set);
void CPU_CLR(unsigned cpu, cpu_set_t* set);
int CPU_ISSET(unsigned cpu, const cpu_set_t* set);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

clang-diagnostic-error
expected unqualified-id

void CPU_CLR(unsigned cpu, cpu_set_t* set);
int CPU_ISSET(unsigned cpu, const cpu_set_t* set);

int CPU_COUNT(const cpu_set_t* set);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

clang-diagnostic-error
redefinition of __sched_cpucount as different kind of symbol

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Clang-Tidy found issue(s) with the introduced code (4/5)

void CPU_CLR(unsigned cpu, cpu_set_t* set);
int CPU_ISSET(unsigned cpu, const cpu_set_t* set);

int CPU_COUNT(const cpu_set_t* set);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

clang-diagnostic-error
expected expression

int CPU_ISSET(unsigned cpu, const cpu_set_t* set);

int CPU_COUNT(const cpu_set_t* set);
int CPU_EQUAL(const cpu_set_t* a, const cpu_set_t* b);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

clang-diagnostic-error
expected )

int CPU_ISSET(unsigned cpu, const cpu_set_t* set);

int CPU_COUNT(const cpu_set_t* set);
int CPU_EQUAL(const cpu_set_t* a, const cpu_set_t* b);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

clang-diagnostic-error
expected parameter declarator

static constexpr unsigned PROCS_PER_GROUP = 64;

struct cpu_topology_entry {
WORD group;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

clang-diagnostic-error
unknown type name WORD


struct cpu_topology_entry {
WORD group;
WORD count;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

clang-diagnostic-error
unknown type name WORD

using cpu_topology = std::vector<cpu_topology_entry>;

struct windows_topology_policy {
static WORD get_group_count() { return GetActiveProcessorGroupCount(); }

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

clang-diagnostic-error
unknown type name WORD

// compiles unchanged.
#include "platform_specific/affinity_win32_adapter.h"

#include "affinity.unix.cc"

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ bugprone-suspicious-include ⚠️
suspicious #include of file with .cc extension

int CPU_COUNT(const cpu_set_t* set);
int CPU_EQUAL(const cpu_set_t* a, const cpu_set_t* b);

int sched_getaffinity(int pid, size_t cpusetsize, cpu_set_t* mask);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ readability-redundant-declaration ⚠️
redundant sched_getaffinity declaration

Suggested change
int sched_getaffinity(int pid, size_t cpusetsize, cpu_set_t* mask);

int CPU_EQUAL(const cpu_set_t* a, const cpu_set_t* b);

int sched_getaffinity(int pid, size_t cpusetsize, cpu_set_t* mask);
int sched_setaffinity(int pid, size_t cpusetsize, const cpu_set_t* mask);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ readability-redundant-declaration ⚠️
redundant sched_setaffinity declaration

Suggested change
int sched_setaffinity(int pid, size_t cpusetsize, const cpu_set_t* mask);

int sched_getaffinity(int pid, size_t cpusetsize, cpu_set_t* mask);
int sched_setaffinity(int pid, size_t cpusetsize, const cpu_set_t* mask);

pthread_t pthread_self();

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ readability-redundant-declaration ⚠️
redundant pthread_self declaration

Suggested change
pthread_t pthread_self();

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Clang-Tidy found issue(s) with the introduced code (5/5)


pthread_t pthread_self();

int pthread_setaffinity_np(pthread_t thread, size_t cpusetsize, const cpu_set_t* mask);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ readability-redundant-declaration ⚠️
redundant pthread_setaffinity_np declaration

Suggested change
int pthread_setaffinity_np(pthread_t thread, size_t cpusetsize, const cpu_set_t* mask);

pthread_t pthread_self();

int pthread_setaffinity_np(pthread_t thread, size_t cpusetsize, const cpu_set_t* mask);
int pthread_getaffinity_np(pthread_t thread, size_t cpusetsize, cpu_set_t* mask);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ readability-redundant-declaration ⚠️
redundant pthread_getaffinity_np declaration

Suggested change
int pthread_getaffinity_np(pthread_t thread, size_t cpusetsize, cpu_set_t* mask);

Comment on lines +62 to +63
WORD group;
WORD count;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ cppcoreguidelines-pro-type-member-init ⚠️
constructor does not initialize these fields: group, count

Suggested change
WORD group;
WORD count;
WORD group{};
WORD count{};

@coveralls

Copy link
Copy Markdown

Coverage Report for CI Build 31012551030

Coverage remained the same at 95.067%

Details

  • Coverage remained the same as the base build.
  • Patch coverage: No coverable lines changed in this PR.
  • No coverage regressions found.

Uncovered Changes

No uncovered changes found.

Coverage Regressions

No coverage regressions found.


Coverage Stats

Coverage Status
Relevant Lines: 7261
Covered Lines: 7156
Line Coverage: 98.55%
Relevant Branches: 3686
Covered Branches: 3251
Branch Coverage: 88.2%
Branches in Coverage %: Yes
Coverage Strength: 2235776.15 hits per line

💛 - Coveralls

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants